const SnakeGame = (function () {
    const Const = {SIZE:15, WIDTH:20, HEIGHT:30, UP: -1, DOWN: 1, LEFT: -2, RIGHT: 2, DELAY: 300};
    const W = Const.WIDTH * Const.SIZE, H = Const.HEIGHT * Const.SIZE;
    let g, snake, food, handler, stopped = false, settings, game, over = false;

    function random () {
        return {
            x:Math.floor(Math.random () * Const.WIDTH - 1) + 1,
            y:Math.floor(Math.random () * Const.HEIGHT - 1) + 1
        };
    }

    function Food () {
        this.color = '#0ff';
        this.pos   = random ();
    }

    Food.prototype = {
        create: function (points) {
            if (!points || !points.length) {
                throw "empty points!"
            }

            let temp = random (), i, p;
            for (i = 0; i < points.length; i ++) {
                p = points [i];
                if (temp.x === p.x && temp.y === p.y) {
                    temp = random ();
                }
            }

            this.pos = temp;
        },
        paint : function (g) {
            if (!g) {return;}
            g.fillStyle = this.color;
            g.fillRect (this.pos.x * Const.SIZE, this.pos.y * Const.SIZE, Const.SIZE, Const.SIZE);
        }
    };

    function _init () {
        let canvas = document.getElementById (settings.canvas);
        g = canvas.getContext ('2d');
        snake = new Snake ();
        food  = new Food ();

        snake.paint (g);
        food.paint (g);
    }

    function _fire (settings, name, invoker) {
        if (settings && (name in settings) && (typeof settings[name] === "function")) {
            settings[name].apply (invoker);
        }
    }

    function Snake () {
        this.points = [{x:4,y:5}, {x:5, y:5}, {x:6, y:5}];
        // this.tail = {};
        this.oldDir = this.newDir = Const.RIGHT;
        this.color = '#f0f';
        this.level =9;
    }

    Snake.prototype = {
        get speed () { return Const.DELAY - this.level * 30},
        paint : function (g) {
            g.fillStyle = this.color;
            for (let i = 0; i < this.points.length; i ++) {
                let p = this.points [i];
                g.fillRect (p.x * Const.SIZE, p.y * Const.SIZE, Const.SIZE, Const.SIZE);
            }
        },
        move: function () {
            if (this.oldDir + this.newDir !== 0) {
                this.oldDir = this.newDir;
            }
            // this.tail = this.points.pop ();
            let x = this.points[0].x, y = this.points[0].y;
            switch (this.oldDir) {
                case Const.UP:
                    y --;
                    break;
                case Const.DOWN:
                    y ++;
                    break;
                case Const.LEFT:
                    x --;
                    break;
                case Const.RIGHT:
                    x ++;
                    break;
            }
            if (!check()) {
                stopped = true;
                if (handler) {
                    clearTimeout (handler);
                    handler = null;
                }
                gameOver ();
            } else {
                this.points.unshift({x, y});
                let level = Math.floor (this.points.length / 5);
                if (this.level !== level) {
                    this.level = level;
                    if (this.level > 10) {
                        this.level = 10;
                    } else {
                        _fire (settings, 'onlevelup', game);
                    }
                }
            }
        }
    }

    function gameOver () {
        if (handler) {
            clearTimeout(handler);
            handler = null;
        }
        stopped = true;
        _fire (settings, 'ongameover', game);
        over = true;
        console.log ('game over');
    }

    function animate () {
        snake.move ();
        repaint ();

        if (handler) {
            clearTimeout (handler);
            handler = null;
        }
        if (!stopped)
            handler = setTimeout (animate, snake.speed);
    }

    function repaint () {
        if (over) {
            g.fillStyle = 'rgba(0, 0, 0, 0.5)';
            g.fillRect (0, 0, W, H);
            g.font = 'bold 36px Arial';
            g.fillStyle = '#fff';
            g.textAlign = 'center';
            g.fillText ('GAME OVER', W/2, H/2);
        } else {
            g.fillStyle = '#333';
            g.fillRect (0, 0, W, H);
            snake.paint (g);
            food.paint (g);
        }
    }

    function check () {
        let head = snake.points[0], i, p;
        if (head.x < 0 || head.x >= Const.WIDTH || head.y < 0 || head.y >= Const.HEIGHT) {
            console.log ('hit the border');
            return false;
        }

        for (i = 1; i < snake.points.length; i ++) {
            p = snake.points[i];
            if (head.x === p.x && head.y === p.y) {
                console.log ('hit myself')
                return false;
            }
        }

        if (head.x === food.pos.x && head.y === food.pos.y) {
            snake.points.push (food.pos);
            food.create (snake.points);
            if (settings && settings.ongrow && typeof settings.ongrow === 'function') {
                settings.ongrow.apply (game);
            }
        }
        return true;
    }

    function fireRunningStateChanged (stopped) {
        if (settings && settings.onRunningStateChanged && typeof settings.onRunningStateChanged === 'function') {
            settings.onRunningStateChanged.apply (game, [stopped]);
        }
    }

    return game = {
        init: function (options) {
            if (!('canvas' in options)) {
                throw "canvas not present";
            }
            settings = options;
            let canvas = document.getElementById (options.canvas);
            canvas.width = Const.SIZE * Const.WIDTH;
            canvas.height = Const.SIZE * Const.HEIGHT;

            _init ();

            document.onkeyup = function (e) {
                switch (e.key) {
                    case "ArrowUp":
                        snake.newDir = Const.UP; break;
                    case "ArrowDown":
                        snake.newDir = Const.DOWN; break;
                    case "ArrowLeft":
                        snake.newDir = Const.LEFT; break;
                    case "ArrowRight":
                        snake.newDir = Const.RIGHT; break;
                    case " ":
                        stopped = !stopped;
                        if (stopped) {
                            if (handler) {
                                clearTimeout (handler);
                                handler = null;
                            }
                        } else {
                            game.start ();
                        }
                        fireRunningStateChanged (stopped);
                        break;
                }
            }

            return this;
        },
        start : function () {
            stopped = false;
            fireRunningStateChanged (stopped);
            window.requestAnimationFrame (animate);
        },
        stop : function () {
            stopped = true;
            fireRunningStateChanged (stopped);
        },
        restart : function () {
            over = false;
            _init ();
            this.start ();
        },
        get level () { return snake.level; },
        get score () { return (snake.points.length - 3) * 10}
    };
})();
export default SnakeGame;